#include "gameview.h"

GameView::GameView(GameModel& model, GuiSystem& gui) : _model(model), _gui(gui),
        _boardModel("..\\..\\models\\default_board.obj"),
        _bgTexture("..\\..\\models\\textures\\background.png"),
        _mouseTexture("..\\..\\models\\textures\\mouse.png", true),
        _animSpeed(2.0f)
{
    glfwGetWindowSize(&_wndWidth, &_wndHeight);

    _pieceModels[W_KING] = new ObjModel("..\\..\\models\\king_white.obj");
    _pieceModels[W_QUEEN] = new ObjModel("..\\..\\models\\queen_white.obj");
    _pieceModels[W_ROOK] = new ObjModel("..\\..\\models\\rook_white.obj");
    _pieceModels[W_BISHOP] = new ObjModel("..\\..\\models\\bishop_white.obj");
    _pieceModels[W_KNIGHT] = new ObjModel("..\\..\\models\\knight_white.obj");
    _pieceModels[W_PAWN] = new ObjModel("..\\..\\models\\pawn_white.obj");

    _pieceModels[B_KING] = new ObjModel("..\\..\\models\\king_black.obj");
    _pieceModels[B_QUEEN] = new ObjModel("..\\..\\models\\queen_black.obj");
    _pieceModels[B_ROOK] = new ObjModel("..\\..\\models\\rook_black.obj");
    _pieceModels[B_BISHOP] = new ObjModel("..\\..\\models\\bishop_black.obj");
    _pieceModels[B_KNIGHT] = new ObjModel("..\\..\\models\\knight_black.obj");
    _pieceModels[B_PAWN] = new ObjModel("..\\..\\models\\pawn_black.obj");

    updateBoard();

    _animation = false;
    _drawMouse = false;
    _mouseX = 0.0f, _mouseY = 0.0f;


    // Create GUI elements

    _gui.addStatic(GUI_HOURGLASS, 0.85f, 0.8f,
        _gui.widthToRel(128), _gui.heightToRel(128),
        *new Texture("..\\..\\models\\textures\\hourglass.png", true));
    _gui.disable(GUI_HOURGLASS);

    _gui.addStatic(GUI_WHITEWON, 0.4f, 0.8f,
        _gui.widthToRel(256), _gui.heightToRel(64),
        *new Texture("..\\..\\models\\textures\\text_whitewon.png", true));
    _gui.disable(GUI_WHITEWON);

    _gui.addStatic(GUI_BLACKWON, 0.4f, 0.8f,
        _gui.widthToRel(256), _gui.heightToRel(64),
        *new Texture("..\\..\\models\\textures\\text_blackwon.png", true));
    _gui.disable(GUI_BLACKWON);

}

GameView::~GameView()
{
    //dtor
}

void GameView::draw(bool clear)
{
    if (clear) {
        glClearColor(0.8f, 0.8f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    } else {
        glClear(GL_DEPTH_BUFFER_BIT);
    }

    // Draw backgroud image
    setup2dView();
    glBindTexture(GL_TEXTURE_2D, _bgTexture.getId());
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 0.0f);
        glVertex2f(0.0f, 0.0f);
        glTexCoord2f(1.0f, 0.0f);
        glVertex2f(1.0f, 0.0f);
        glTexCoord2f(1.0f, 1.0f);
        glVertex2f(1.0f, 1.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex2f(0.0f, 1.0f);
    glEnd();
    glBindTexture(GL_TEXTURE_2D, 0);

    setup3dView();

    Vector3f posVec = _camera.getPosition();
    float pos[4] = {posVec.x, posVec.y, posVec.z, 0.0f};
    glLightfv(GL_LIGHT0, GL_POSITION, pos);

    // Draw board
    Matrix4f mBoard = Matrix4f::createTranslation(0.0f, 0.0f, -0.1f);
    drawModel(_boardModel, mBoard);

    // Draw highlights
    for (std::vector<HighlightInfo>::iterator i = _highlights.begin();
            i != _highlights.end(); ++i) {
        drawHighlight(i->_x, i->_y, i->_type);
    }

    // Draw mouse
    drawMouseMarker();

    // Draw pieces
    for (std::vector<PieceInfo>::iterator i = _pieces.begin();
            i != _pieces.end(); ++i) {
        if (!i->_hide) {
            if (i->_anim == AT_DELETE) {
                drawModel(*_pieceModels[i->_type], i->_trans, true, i->_t);
            } else {
                drawModel(*_pieceModels[i->_type], i->_trans);
            }
        }
    }

    // Draw gui
    _gui.draw();

    setup3dView();

}

void GameView::setup3dView()
{
    glViewport(0, 0, _wndWidth, _wndHeight);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(65.0f, (GLfloat)_wndWidth/(GLfloat)_wndHeight, 1.0f, 100.0f);

    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDisable(GL_COLOR_MATERIAL);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    float matAmbient[4] = {1.0f, 1.0f, 1.0f, 1.0f};
    float matDiffuse[4] = {1.0f, 1.0f, 1.0f, 1.0f};
    float matSpecular[4] = {0.0f, 0.0f, 0.0f, 0.0f};
    float matEmission[4] = {0.0f, 0.0f, 0.0f, 0.0f};
    glMaterialfv(GL_FRONT, GL_AMBIENT, matAmbient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, matDiffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, matSpecular);
    glMaterialfv(GL_FRONT, GL_EMISSION, matEmission);

    float lightAmbient[4] = {0.3f, 0.3f, 0.3f, 1.0f};
    float lightDiffuse[4] = {1.0f, 1.0f, 1.0f, 1.0f};
    float lightPos[4] = {0.0f, 0.0f, 10.0f, 0.0f};
    glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    _camera.recalculate();

}

void GameView::setup2dView()
{
    glDisable(GL_CULL_FACE);
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0f, 1.0f, 0.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

}

void GameView::onEnter()
{
    setup3dView();
}

Camera& GameView::getCamera()
{
    return _camera;
}

bool GameView::getClickedSquare(int wndX, int wndY, int* squareX, int* squareY)
{
    GLint viewport[4];
    GLdouble modelview[16], projection[16];

    _camera.recalculate();

    glGetIntegerv(GL_VIEWPORT, viewport);
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);

    GLdouble objX, objY, objZ;
    gluUnProject((float)wndX, (float)wndY, 1.0f,
                 modelview, projection, viewport,
                 &objX, &objY, &objZ);

    // Calculate intersection with XY-plane
    float px = _camera.getPosition().x - objX;
    float py = _camera.getPosition().y - objY;
    float pz = _camera.getPosition().z - objZ;

    float x = objX + px*(-objZ / pz);
    float y = objY + py*(-objZ / pz);

    if (x < -8.0f || x > 8.0f || y < -8.0f || y > 8.0f) {
        return false;
    }

    int tmpX = (int)((y + 8) / 2);
    int tmpY = (int)((x + 8) / 2);

    if (tmpX < 0 || tmpX > 7 || tmpY < 0 || tmpY > 7) {
        return false;
    }

    //printf("%d, %d\n", tmpX, tmpY);

    *squareX = tmpX;
    *squareY = tmpY;

    return true;
}

void GameView::drawModel(ObjModel& model, Matrix4f& matrix,
                         bool isTrans, float t)
{
    glBindBuffer(GL_ARRAY_BUFFER, model.getVboId());

    glVertexPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(0));
    glNormalPointer(GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(12));
    glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(24));

    if (isTrans) {
        glEnable(GL_BLEND);
        glBlendColor(t, t, t, t);
        glBlendFunc(GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_COLOR);
    }

    glBindTexture(GL_TEXTURE_2D, model.getTexture().getId());

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
        glMultMatrixf(matrix.data);
        glDrawArrays(GL_TRIANGLES, 0, model.getNumVertices());
    glPopMatrix();

    if (isTrans) {
        glDisable(GL_BLEND);
    }

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

void GameView::drawAxes(Vector3f position, float length)
{
    glDisable(GL_LIGHTING);
    //glDisable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_2D);

    glLineWidth(5.0f);
    glBegin(GL_LINES);
        glColor4f(1.0f, 0.0f, 0.0f, 0.0f);
        glVertex3f(position.x, position.y, position.z);
        glVertex3f(position.x + length, position.y, position.z);

        glColor4f(0.0f, 1.0f, 0.0f, 0.0f);
        glVertex3f(position.x, position.y, position.z);
        glVertex3f(position.x, position.y + length, position.z);

        glColor4f(0.0f, 0.0f, 1.0f, 0.0f);
        glVertex3f(position.x, position.y, position.z);
        glVertex3f(position.x, position.y, position.z + length);
    glEnd();

    glEnable(GL_LIGHTING);
    //glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
}

void GameView::drawHighlight(int squareX, int squareY, HighlightType type)
{
    float x = (float)(squareY * 2 - 8);
    float y = (float)(squareX * 2 - 8);

    //glEnable(GL_BLEND);
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_2D);

    if (type == HLT_MOVE) {
        glColor4f(0.3f, 0.8f, 0.3f, 1.0f);
    } else if (type == HLT_SELF) {
        glColor4f(0.0f, 0.0f, 0.8f, 1.0f);
    } else if (type == HLT_CASTLE) {
        glColor4f(1.0f, 0.8f, 0.0f, 1.0f);
    } else if (type == HLT_ATTACK) {
        glColor4f(0.8f, 0.3f, 0.3f, 1.0f);
    }

    glBegin(GL_QUADS);
        glVertex3f(x, y, 0.0f);
        glVertex3f(x + 2.0f, y, 0.0f);
        glVertex3f(x + 2.0f, y + 2.0f, 0.0f);
        glVertex3f(x, y + 2.0f, 0.0f);
    glEnd();

    glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
    glLineWidth(1.0f);
    glBegin(GL_LINE_LOOP);
        glVertex3f(x, y, 0.0f);
        glVertex3f(x + 2.0f, y, 0.0f);
        glVertex3f(x + 2.0f, y + 2.0f, 0.0f);
        glVertex3f(x, y + 2.0f, 0.0f);
    glEnd();


    //glDisable(GL_BLEND);
    glEnable(GL_LIGHTING);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);

}

void GameView::highlightMoves(int playerX, int playerY)
{
    _highlights.clear();

    // Check every move
    for (int i = 0; i < _model.getNumMoves(); ++i) {

        const Move& m = _model.getMove(i);

        // Castling
        if (m.getX1() == playerX && m.getY1() == playerY &&
                (m.getSpecialRule() == CASTLESHORT ||
                m.getSpecialRule() == CASTLELONG)) {
            HighlightInfo h;
            h._type = HLT_CASTLE;
            h._x = m.getX2();
            h._y = m.getY2();
            _highlights.push_back(h);

            // Attack square
        } else if(m.getX1() == playerX && m.getY1() == playerY &&
                  _model.getPiece(m.getX2(), m.getY2()) != EMPTY) {
            HighlightInfo h;
            h._type = HLT_ATTACK;
            h._x = m.getX2();
            h._y = m.getY2();
            _highlights.push_back(h);

            // Passant
        } else if(m.getX1() == playerX && m.getY1() == playerY &&
                  m.getSpecialRule() == PASSAT) {
            HighlightInfo h;
            h._type = HLT_ATTACK;
            h._x = m.getX2();
            h._y = m.getY2();
            _highlights.push_back(h);

            // Move to square
        } else if (m.getX1() == playerX && m.getY1() == playerY) {
            HighlightInfo h;
            h._type = HLT_MOVE;
            h._x = m.getX2();
            h._y = m.getY2();
            _highlights.push_back(h);
        }
    }

    // Highlight the player square
    HighlightInfo hPlayer;
    hPlayer._type = HLT_SELF;
    hPlayer._x = playerX;
    hPlayer._y = playerY;
    _highlights.push_back(hPlayer);

}

void GameView::clearHighlights()
{
    _highlights.clear();
}

void GameView::animateMove(const Move& m)
{
    _animation = true;
    _pieces.clear();

    std::vector<std::pair<int, int> > anims;

    // Normal move, promotion or enpassant
    if (m.getSpecialRule() != CASTLESHORT && m.getSpecialRule() != CASTLELONG) {

        // Moving piece
        PieceInfo p;
        p._type = _model.getPiece(m.getX1(), m.getY1());
        p._x0 = m.getX1(), p._y0 = m.getY1();
        p._x1 = m.getX2(), p._y1 = m.getY2();
        p._t = 0.0f;
        p._anim = AT_MOVE;
        p._hide = false;
        updateMatrixForPiece(p);
        _pieces.push_back(p);
        anims.push_back(std::pair<int, int> (m.getX1(), m.getY1()));

        // Normal capture
        if (_model.getPiece(m.getX2(), m.getY2()) != EMPTY) {

            // Piece being captured
            PieceInfo p;
            p._type = _model.getPiece(m.getX2(), m.getY2());
            p._x0 = m.getX2(), p._y0 = m.getY2();
            p._x1 = 0, p._y1 = 0;
            p._t = 0.0f;
            p._anim = AT_DELETE;
            p._hide = false;
            updateMatrixForPiece(p);
            _pieces.push_back(p);
            anims.push_back(std::pair<int, int> (m.getX2(), m.getY2()));

            // En passant capture
        } else if(m.getSpecialRule() == PASSAT) {

            // Piece being captured
            int cx = m.getX2();
            int cy = m.getY1();

            p._type = _model.getPiece(cx, cy);
            p._x0 = cx, p._y0 = cy;
            p._x1 = 0, p._y1 = 0;
            p._anim = AT_DELETE;
            updateMatrixForPiece(p);
            _pieces.push_back(p);
            anims.push_back(std::pair<int, int> (cx, cy));

        }

        // Castling
    } else if (m.getSpecialRule() == CASTLESHORT ||
            m.getSpecialRule() == CASTLELONG) {

        // King
        PieceInfo p;
        p._type = _model.getPiece(m.getX1(), m.getY1());
        p._x0 = m.getX1(), p._y0 = m.getY1();
        p._x1 = m.getX2(), p._y1 = m.getY2();
        p._t = 0.0f;
        p._anim = AT_MOVE;
        p._hide = false;
        updateMatrixForPiece(p);
        _pieces.push_back(p);
        anims.push_back(std::pair<int, int> (m.getX1(), m.getY1()));

        // Rook
        int rx1, rx2;
        if (m.getSpecialRule() == CASTLELONG) {
            rx1 = 0;
            rx2 = 3;
        } else {
            rx1 = 7;
            rx2 = 5;
        }
        p._type = _model.getPiece(rx1, m.getY1());
        p._x0 = rx1, p._y0 = m.getY1();
        p._x1 = rx2, p._y1 = m.getY1();
        updateMatrixForPiece(p);
        _pieces.push_back(p);
        anims.push_back(std::pair<int, int> (rx1, m.getY1()));


    }

    // TODO: handle animation when piece is promoted

    // Add all remaining static pieces to list
    for (int i = 0; i < 8; ++i) {
        for (int j = 0; j < 8; ++j) {

            if (_model.getPiece(i, j) != EMPTY) {
                bool skip = false;

                for (std::vector<std::pair<int, int> >::iterator p = anims.begin();
                        p != anims.end(); ++p) {
                    if (p->first == i && p->second == j) {
                        skip = true;
                    }
                }

                if (!skip) {
                    PieceInfo p;
                    p._type = _model.getPiece(i, j);
                    p._x0 = i, p._y0 = j;
                    p._x1 = 0, p._y1 = 0;
                    p._t = 0.0f;
                    p._anim = AT_NONE;
                    p._hide = false;
                    updateMatrixForPiece(p);
                    _pieces.push_back(p);
                }

            }
        }
    }
}

void GameView::updateAnimations(float dt)
{
    bool animationPlaying = false;

    for (std::vector<PieceInfo>::iterator i = _pieces.begin();
            i != _pieces.end(); ++i) {

        if (i->_anim == AT_MOVE) {
            animationPlaying = true;

            i->_t += dt * _animSpeed;
            if (i->_t >= 1.0f) {
                i->_anim = AT_NONE;
                i->_x0 = i->_x1;
                i->_y0 = i->_y1;
                i->_x1 = 0;
                i->_y1 = 0;
                i->_t = 0.0f;
            }
            updateMatrixForPiece(*i);

        } else if (i->_anim == AT_DELETE) {
            animationPlaying = true;

            i->_t += dt * _animSpeed;
            if (i->_t >= 1.0f) {
                i->_anim = AT_NONE;
                i->_hide = true;
            }
        }
    }

    if (!animationPlaying) {
        _animation = false;
    }
}

void GameView::showMouseMarker(int x, int y)
{
    _drawMouse = true;
    _mouseX = squareToObj(y) - 1.0f;
    _mouseY = squareToObj(x) - 1.0f;
}

void GameView::hideMouseMarker()
{
    _drawMouse = false;
}

void GameView::drawMouseMarker()
{
    if (_drawMouse) {

        glDisable(GL_LIGHTING);
        glDisable(GL_DEPTH_TEST);
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

        glBindTexture(GL_TEXTURE_2D, _mouseTexture.getId());
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3f(_mouseX, _mouseY, 0.0f);
            glTexCoord2f(1.0f, 0.0f);
            glVertex3f(_mouseX + 2.0f, _mouseY, 0.0f);
            glTexCoord2f(1.0f, 1.0f);
            glVertex3f(_mouseX + 2.0f, _mouseY + 2.0f, 0.0f);
            glTexCoord2f(0.0f, 1.0f);
            glVertex3f(_mouseX, _mouseY + 2.0f, 0.0f);
        glEnd();
        glBindTexture(GL_TEXTURE_2D, 0);


        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        glDisable(GL_BLEND);
        glEnable(GL_LIGHTING);
        glEnable(GL_DEPTH_TEST);
    }

}

void GameView::updateBoard()
{
    _pieces.clear();

    for (int i = 0; i < 8; ++i) {
        for (int j = 0; j < 8; ++j) {
            int type = _model.getPiece(i, j);
            if (type != EMPTY) {

                PieceInfo p;
                p._type = type;
                p._x0 = i, p._y0 = j;
                p._x1 = 0, p._y1 = 0;
                p._t = 0.0f;
                p._anim = AT_NONE;
                p._hide = false;
                updateMatrixForPiece(p);
                _pieces.push_back(p);
            }
        }
    }
}

float GameView::squareToObj(int square)
{
    return (float)(square * 2 - 7);
}

void GameView::updateMatrixForPiece(PieceInfo& p)
{
    int color = _model.getPieceColor(p._type);
    p._trans.identity();

    if (p._anim == AT_NONE || p._anim == AT_DELETE) {
        Vector3f v(squareToObj(p._y0), squareToObj(p._x0), 0.0f);
        p._trans.setTranslation(v);

    } else if (p._anim == AT_MOVE) {
        Vector3f vStart(squareToObj(p._y0), squareToObj(p._x0), 0.0f);
        Vector3f vEnd(squareToObj(p._y1), squareToObj(p._x1), 0.0f);
        Vector3f vCur = vStart.lerp(p._t, vEnd);
        p._trans.setTranslation(vCur);
    }

    Matrix3f mRot;
    if (color == BLACK) {
        mRot = Matrix3f::createRotationAroundAxis(0.0f, 0.0f, -M_PI_2);
    } else {
        mRot = Matrix3f::createRotationAroundAxis(0.0f, 0.0f, M_PI_2);
    }
    p._trans.setRotation(mRot);

}

bool GameView::isReady()
{
    return !_animation;
}

void GameView::reset()
{
    //_pieces.clear();
    _highlights.clear();
    updateBoard();

    _animation = false;
    _drawMouse = false;
    _mouseX = 0.0f, _mouseY = 0.0f;

    _gui.disable(GUI_HOURGLASS);
    _gui.disable(GUI_WHITEWON);
    _gui.disable(GUI_BLACKWON);

}
